package com.wao.information.service;

import com.wao.base.util.BeanUtils;
import com.wao.information.domain.Information;
import com.wao.information.repository.InformationRepository;
import com.wao.information.service.dto.InformationDTO;
import com.wao.information.service.mapper.InformationMapper;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanWrapperImpl;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;

import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;


/**
 * Service Implementation for managing Information.
 */
@Service
@Transactional
public class InformationService {

    private final Logger log = LoggerFactory.getLogger(InformationService.class);

    private final String IMG_SOURCE_STR = "<X_X_X";

    private final String IMG_TARGET_STR = "<img";

    private final InformationRepository informationRepository;

    private final InformationMapper informationMapper;

    public InformationService(InformationRepository informationRepository, InformationMapper informationMapper) {
        this.informationRepository = informationRepository;
        this.informationMapper = informationMapper;
    }

    /**
     * Save a information.
     *
     * @param informationDTO the entity to save
     * @return the persisted entity
     */
    public InformationDTO save(InformationDTO informationDTO) {
        log.debug("Request to save Information : {}", informationDTO);
        Information information = informationMapper.toEntity(informationDTO);
        if (StringUtils.isNotBlank(information.getContent()) && information.getContent().contains(IMG_SOURCE_STR))
            information.setContent(information.getContent().replace(IMG_SOURCE_STR, IMG_TARGET_STR));
        information = informationRepository.save(information);
        return informationMapper.toDto(information);
    }

    /**
     * Get all the information.
     *
     * @param pageable the pagination information
     * @return the list of entities
     */
    @Transactional(readOnly = true)
    public Page<InformationDTO> findAll(Pageable pageable) {
        log.debug("Request to get all Information");
        return informationRepository.findAll(pageable)
                .map(informationMapper::toDto);
    }

    /**
     * Get all the information by keyword.
     *
     * @param keyword
     * @param categoryId
     * @param pageable   the pagination information
     * @return the list of entities
     */
    @Transactional(readOnly = true)
    public Page<InformationDTO> findByKeyword(String keyword, Long categoryId, Pageable pageable) {
        log.debug("Request to get all Information by keyword");
        return informationRepository.findAll(createKeywordSpecification(keyword, categoryId), pageable)
                .map(informationMapper::toDto);
    }

    /**
     * Get one information by id.
     *
     * @param id the id of the entity
     * @return the entity
     */
    @Transactional(readOnly = true)
    public InformationDTO findOne(Long id) {
        log.debug("Request to get Information : {}", id);
        Information information = informationRepository.findOne(id);
        return informationMapper.toDto(information);
    }

    /**
     * Delete the  information by id.
     *
     * @param id the id of the entity
     */
    public void delete(Long id) {
        log.debug("Request to delete Information : {}", id);
        informationRepository.delete(id);
    }

    /**
     * Get the next Information.
     *
     * @param categoryId the categoryId of a information
     * @return the list of entities
     */
    @Transactional(readOnly = true)
    public Page<InformationDTO> findByCategory(Long categoryId, Pageable pageable) {
        log.debug("Request to get a list of Information by Category : {}", categoryId);
        return informationRepository.findByCategoryId(categoryId, pageable)
                .map(informationMapper::toDto);
    }

    /**
     * Get a list of Information by Category.
     *
     * @param id the id of a information
     * @return the list of entities
     */
    @Transactional(readOnly = true)
    public InformationDTO findNext(Long id, Pageable pageable) {
        log.debug("Request to get next for Information: {}", id);
        //TODO 下一篇逻辑优化
        Information information = informationRepository.findOne(id);
        if (information == null)
            return null;
        Sort sort = new Sort("displayOrder");
        if (pageable != null && pageable.getSort() != null)
            sort = pageable.getSort();
        Pageable newPage = new PageRequest(0, 2, sort);
        Page<Information> page = informationRepository.findAll(createOrderSpecification(information, sort), newPage);
        if (page != null && page.hasContent() && page.getContent().size() >= 2)
            return informationMapper.toDto(page.getContent().get(1));
        return null;
    }

    private Specification<Information> createKeywordSpecification(String name, Long categoryId) {
        return new Specification<Information>() {
            @Override
            public Predicate toPredicate(Root<Information> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
                List<Predicate> predicate = new ArrayList<>();
                if (name != null)
                    predicate.add(cb.like(root.get("name"), "%" + name + "%"));
                if (categoryId != null)
                    predicate.add(cb.equal(root.get("categoryId"), categoryId));
                Predicate[] pre = new Predicate[predicate.size()];
                return query.where(predicate.toArray(pre)).getRestriction();
            }
        };
    }

    private Specification<Information> createOrderSpecification(Information information, Sort sort) {
        Assert.notNull(information, "Information must not be null!");
        Assert.notNull(sort, "Sort must not be null!");
        return new Specification<Information>() {
            @Override
            public Predicate toPredicate(Root<Information> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
                List<Predicate> predicate = new ArrayList<>();
                BeanWrapperImpl bws = new BeanWrapperImpl(information);
                for (Sort.Order order : sort) {
                    Assert.isTrue(BeanUtils.hasProperty(information.getClass(), order.getProperty()),
                            "No property " + order.getProperty() + " found in class Information.");
                    Object value = bws.getPropertyValue(order.getProperty());
                    Class<?> clazz = bws.getPropertyType(order.getProperty());
                    if (clazz != null) {
                        if ("Integer".equals(clazz.getSimpleName())) {
                            if (order.isAscending()) {
                                predicate.add(cb.greaterThanOrEqualTo(root.get(order.getProperty()), (Integer) value));
                            } else {
                                predicate.add(cb.lessThanOrEqualTo(root.get(order.getProperty()), (Integer) value));
                            }
                        } else if ("Instant".equals(clazz.getSimpleName())) {
                            if (order.isAscending()) {
                                predicate.add(cb.greaterThanOrEqualTo(root.get(order.getProperty()), (Instant) value));
                            } else {
                                predicate.add(cb.lessThanOrEqualTo(root.get(order.getProperty()), (Instant) value));
                            }
                        } else if ("String".equals(clazz.getSimpleName())) {
                            if (order.isAscending()) {
                                predicate.add(cb.greaterThanOrEqualTo(root.get(order.getProperty()), (String) value));
                            } else {
                                predicate.add(cb.lessThanOrEqualTo(root.get(order.getProperty()), (String) value));
                            }
                        } else if ("Long".equals(clazz.getSimpleName())) {
                            if (order.isAscending()) {
                                predicate.add(cb.greaterThanOrEqualTo(root.get(order.getProperty()), (Long) value));
                            } else {
                                predicate.add(cb.lessThanOrEqualTo(root.get(order.getProperty()), (Long) value));
                            }
                        } else if ("Boolean".equals(clazz.getSimpleName())) {
                            if (order.isAscending()) {
                                predicate.add(cb.greaterThanOrEqualTo(root.get(order.getProperty()), (Boolean) value));
                            } else {
                                predicate.add(cb.lessThanOrEqualTo(root.get(order.getProperty()), (Boolean) value));
                            }
                        }
                    }
                }
                Predicate[] pre = new Predicate[predicate.size()];
                return query.where(predicate.toArray(pre)).getRestriction();
            }
        };
    }

}
